window.onload = function () {
  var canvas = document.getElementById("canvas"),
    // returns a drawing context on the canvas
    context = canvas.getContext("2d"),
    strokeStyleSelect = document.getElementById("strokeStyleSelect"),
    fillStyleSelect = document.getElementById("fillStyleSelect"),
    drawRadio = document.getElementById("drawRadio"),
    eraserRadio = document.getElementById("eraserRadio"),
    eraserShapeSelect = document.getElementById("eraserShapeSelect"),
    eraserWidthSelect = document.getElementById("eraserWidthSelect"),
    // eraser
    eraser_line_width = 1,
    eraser_shadow_style = "rgba(0,0,0)",
    eraser_stroke_style = "rgba(255,255,255)",
    eraser_shadow_offset = -5,
    eraser_shadow_blur = 20,
    // grid
    grid_horizontal_spacing = 10,
    grid_vertical_spacing = 10,
    grid_line_color = "lightblue",
    grid_fill_color = "lightgray",
    drawingSurfaceImageData,
    // position
    lastX,
    lastY,
    mousedown = {},
    rubberbandRect = {},
    dragging = false,
    guidewires = true;

  function windowToCanvas(x, y) {
    // 方法返回元素的大小及其相对于视口的位置
    var bbox = canvas.getBoundingClientRect();
    return {
      x: x - bbox.left * (canvas.width / bbox.width),
      y: y - bbox.top * (canvas.height / bbox.height),
    };
  }

  function saveDrawingSurface() {
    //返回一个ImageData对象，用来描述canvas区域隐含的像素数据，这个区域通过矩形表示，起始点为(sx, sy) 、宽为sw、高为sh
    drawingSurfaceImageData = context.getImageData(
      0,
      0,
      canvas.width,
      canvas.height
    );
  }

  function restoreDrawingSurface() {
    //是 Canvas 2D API 将数据从已有的 ImageData 对象绘制到位图的方法。 如果提供了一个绘制过的矩形，则只绘制该矩形的像素。此方法不受画布转换矩阵的影响。
    // ctx.putImageData(imagedata, dx, dy)
    // dx
    // 源图像数据在目标画布中的位置偏移量（x 轴方向的偏移量）。
    // dy
    // 源图像数据在目标画布中的位置偏移量（y 轴方向的偏移量）。
    context.putImageData(drawingSurfaceImageData, 0, 0);
  }

  function updateRubberbandRectangle(loc) {
    rubberbandRect.width = Math.abs(loc.x - mousedown.x);
    rubberbandRect.height = Math.abs(loc.y - mousedown.y);
    if (loc.x > mousedown.x) {
      rubberbandRect.left = mousedown.x;
    } else {
      rubberbandRect.left = loc.x;
    }
    if (loc.y > mousedown.y) {
      rubberbandRect.top = mousedown.y;
    } else {
      rubberbandRect.top = loc.y;
    }
  }

  function drawRubberbandShape(loc) {
    // atan 反正切函数
    var angle = Math.atan(rubberbandRect.height / rubberbandRect.width),
      radius = rubberbandRect.height / Math.sin(angle);
    if (mousedown.y === loc.y) {
      radius = Math.abs(loc.x - mousedown.x);
    }
    context.beginPath();
    // 是 Canvas 2D API 绘制圆弧路径的方法。 圆弧路径的圆心在 (x, y) 位置，半径为 r ，根据anticlockwise （默认为顺时针）指定的方向从 startAngle 开始绘制，到 endAngle 结束。
    // ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
    context.arc(mousedown.x, mousedown.y, radius, 0, Math.PI * 2, false);
    // 是 Canvas 2D API 使用非零环绕规则，根据当前的画线样式，绘制当前或已经存在的路径的方法。
    // context.strokeStyle = "blue";
    context.stroke();
    // 是 Canvas 2D API 根据当前的填充样式，填充当前或已存在的路径的方法。采取非零环绕或者奇偶环绕规则。
    // context.fillStyle = "blue";
    context.fill();
  }

  function updateRubberband(loc) {
    updateRubberbandRectangle(loc);
    drawRubberbandShape(loc);
  }

  // guidewires
  function drawGuidWires(x, y) {
    context.save();
    context.strokeStyle = "rgba(0,0,230,0.4)";
    context.lineWidth = 0.5;
    drawVerticalLine(x);
    drawHorizontalLine(y);
    context.restore();
  }

  function drawVerticalLine(x) {
    context.beginPath();
    context.moveTo(x + 0.5, 0);
    context.lineTo(x + 0.5, context.canvas.height);
    context.strokeStyle = "red";
    context.stroke();
  }
  function drawHorizontalLine(y) {
    context.beginPath();
    context.moveTo(0, y + 0.5);
    context.lineTo(context.canvas.width, y + 0.5);
    context.strokeStyle = "red";
    context.stroke();
  }

  function drawGrid(color, stepx, stepy) {
    context.strokeStyle = color;
    context.lineWidth = 0.5;

    // 画纵向的参考线
    for (var i = stepx + 0.5; i < context.canvas.width; i = i + stepx) {
      context.beginPath();
      context.moveTo(i, 0);
      context.lineTo(i, context.canvas.height);
      context.stroke();
    }

    // 画横向的参考线
    for (var i = stepy + 0.5; i < context.canvas.height; i = i + stepy) {
      context.beginPath();
      context.moveTo(0, i);
      context.lineTo(0, context.canvas.width);
      context.stroke();
    }
  }

  // eraser 相关
  function setErasePathForEraser(loc) {
    var eraserWidth = parseFloat(eraserWidthSelect.value);
    context.beginPath();
    if (eraserShapeSelect.value === "circle") {
      context.arc(lastX, lastY, eraserWidth / 2, 0, Math.PI * 2, false);
    } else {
      // void ctx.rect(x, y, width, height);

      // x
      // 矩形起点的 x 轴坐标。
      // y
      // 矩形起点的 y 轴坐标。
      // width
      // 矩形的宽度。
      // height
      // 矩形的高度。

      context.rect(lastX, lastY, eraserWidth, eraserWidth);
    }
    // CanvasRenderingContext2D.clip() 是 Canvas 2D API 将当前创建的路径设置为当前剪切路径的方法。
    context.clip();
  }

  function setDrawPathForEraser(loc) {
    var eraserWidth = parseFloat(eraserWidthSelect.value);
    context.beginPath();
    if (eraserShapeSelect.value === "circle") {
      context.arc(loc.x, loc.y, eraserWidth / 2, 0, Math.PI * 2, false);
    } else {
      // void ctx.rect(x, y, width, height);

      // x
      // 矩形起点的 x 轴坐标。
      // y
      // 矩形起点的 y 轴坐标。
      // width
      // 矩形的宽度。
      // height
      // 矩形的高度。

      context.rect(loc.x, loc.y, eraserWidth, eraserWidth);
    }
    // CanvasRenderingContext2D.clip() 是 Canvas 2D API 将当前创建的路径设置为当前剪切路径的方法。
    context.clip();
  }
  function setEraserAttributes() {
    context.lineWidth = eraser_line_width;
    context.shadowColor = eraser_shadow_style;
    context.shadowOffsetX = eraser_shadow_offset;
    context.shadowOffsetY = eraser_shadow_offset;
    context.shadowBlur = eraser_shadow_blur;
    context.strokeStyle = eraser_stroke_style;
  }
  function eraseLast() {
    context.save();
    // 设置橡皮擦的剪裁区域
    setErasePathForEraser();

    /**
     * 两个思路：
     * 1. 设置fill原理是在裁剪区域内重新绘制带填充色的grid，从视觉上是擦除
     * 2. 调用context.clearRect() 清除
     */

    // context.fillStyle = grid_fill_color;
    // context.fillRect(0, 0, context.canvas.width, context.canvas.height);
    // 调用之前必须设置剪裁区域，否则clearRect会将整个画布的内容清除掉
    context.clearRect(0, 0, canvas.width, canvas.height);
    // 画参考线
    drawGrid(grid_line_color, grid_horizontal_spacing, grid_vertical_spacing);

    context.restore();
  }
  function drawEraser(loc) {
    context.save();

    setEraserAttributes();
    setDrawPathForEraser(loc);

    context.stroke();
    context.restore();
  }
  // 事件相关
  canvas.onmousedown = function (e) {
    var loc = windowToCanvas(e.clientX, e.clientY);
    console.log(loc);
    mousedown.x = loc.x;
    mousedown.y = loc.y;
    lastX = loc.x;
    lastY = loc.y;
    dragging = true;
    if (drawRadio.checked) {
      saveDrawingSurface();
    }
  };

  canvas.onmousemove = function (e) {
    var loc;
    if (dragging) {
      e.preventDefault();
      loc = windowToCanvas(e.clientX, e.clientY);
      if (drawRadio.checked) {
        restoreDrawingSurface();
        updateRubberband(loc);
        if (guidewires) {
          // 画图过程中 横纵参考线
          drawGuidWires(loc.x, loc.y);
        }
      } else {
        eraseLast();
        drawEraser(loc);
      }
      lastX = loc.x;
      lastY = loc.y;
    }
  };
  canvas.onmouseup = function (e) {
    var loc = windowToCanvas(e.clientX, e.clientY);
    if (drawRadio.checked) {
      restoreDrawingSurface();
      updateRubberband(loc);
    } else {
      eraseLast();
    }
    dragging = false;
  };

  strokeStyleSelect.onchange = function () {
    context.strokeStyle = strokeStyleSelect.value;
  };
  fillStyleSelect.onchange = function () {
    context.fillStyle = fillStyleSelect.value;
  };
  // 画参考线
  drawGrid(grid_line_color, grid_horizontal_spacing, grid_vertical_spacing);
  context.strokeStyle = strokeStyleSelect.value;
  context.fillStyle = fillStyleSelect.value;
};
